package genetic_algorithm;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class Population {

	private List<ChromosomeFitnessPair> pool; // chromosome pool of current generation
	/**
	 * Constructor- initializes a new population 
	 * @param chromosomesList chromosomes of the first generation
	 * @param fitFun object implementing the domain-specific fitness function
	 */
	public Population(List<Chromosome> chromosomesList,
			FitnessFunction fitFun) {

		// initialize the chromosome pool with given chromosomes
		pool = new LinkedList<ChromosomeFitnessPair>();
		for (Chromosome c : chromosomesList) {
			pool.add(new ChromosomeFitnessPair(c, fitFun.getFitness(c)));
		}
	}
	
	public Population(FitnessFunction fitFun)
	{
		pool = new LinkedList<ChromosomeFitnessPair>();
	}

	/**
	 * Returns the index'th chromosome in the entire chromosome population 
	 * @param index ordinal number of wanted chromosome
	 * @return index'th chromosome, or null if index is illegal
	 */
	public Chromosome getChromosome(int index) {
		return pool.get(index).chromosome;
	}

	/**
	 * Returns fitness function value of index'th chromosome 
	 * @param index ordinal number of chromosome to calculate its fitness
	 * @return fitness function value of index'th chromosome
	 * @throws IllegalArgumentException if index is illegal value
	 */
	public double getFitnessOfChromosome(int index)
			throws IllegalArgumentException {
		
		// check index validity
		if (index < 0 || index >= pool.size()) {
			throw new IllegalArgumentException();
		}
		
		// return fitness value
		return pool.get(index).fitness;
	}

	/**
	 * Returns Population size 
	 * @return number of chromosomes within the population
	 */
	public int getSize() {
		return pool.size();
	}

	/**
	 * Adds given chromosome to the population.
	 * Gets the fitness function in order to calculate the chromosome's
	 * fitness value
	 * @param newChromosome chromosome to be added to the population
	 * @param fitFun object implementing fitness function
	 */
	public void addChromosome(Chromosome newChromosome, FitnessFunction fitFun) {
		pool.add(new ChromosomeFitnessPair(newChromosome, fitFun
				.getFitness(newChromosome)));
	}
	
	/**
	 * Adds given chromosome to the population.
	 * @param newChromosome chromosome to be added to the population
	 * @param fitness chromosome's fitness value
	 */
	public void addChromosome(Chromosome newChromosome, double fitness) {
		pool.add(new ChromosomeFitnessPair(newChromosome, fitness));
	}
	
	/**
	 * Returns chromosome with given index.
	 * Index is according to sorting by fitness value
	 * If index is illegal, returns False. Otherwise, returns True
	 * @param index index of chromosome to remove
	 * @return True if could remove chromosome, False if not
	 */
	public boolean removeChromosome(int index) {
		
		// check index validity
		if (index < 0 || index >= pool.size()) {
			return false;
		}
		
		// chromosome is removed successfully
		pool.remove(index);
		return true;
	}
	
	//TODO COMMENTS
	public boolean removeChromosome(Chromosome ch) {

		// iterate over the chromosomes
		Iterator<ChromosomeFitnessPair> itr = pool.iterator();
		while (itr.hasNext()) {
			
			// compare current chromosome to given one
			Chromosome currCh = itr.next().chromosome;			
			if (currCh.equals(ch)) {
				
				// match is found, remove chromosome
				itr.remove();
				
				// chromosome is removed successfully
				return true;
			}
		}
		
		// could not find given chromosome		
		return false;
	}
	
	/**
	 * Ranks chromosomes according to fitness function value
	 */
	public void sort() {
		Collections.sort(pool);
	}
		
	public Chromosome getBestChromosome() {
		sort();
		return pool.get(pool.size()-1).chromosome;
	}
	
	public double getBestFitness() {
		sort();
		return pool.get(pool.size()-1).fitness;
	}	
	
	public double getWorstFitness() {
		sort();
		return pool.get(0).fitness;
	}	
	
	public double getFitnessAverage() {
		double result = 0;
		for(ChromosomeFitnessPair c : pool)
		{
			result+=c.fitness;
		}
		return result/getSize();
	}	
	
	public int getAmountOfForeignChromosomes() {
		Set<Chromosome> set = new HashSet<Chromosome>();
		for(ChromosomeFitnessPair pair : pool)
		{
			set.add(pair.chromosome);
		}
		return set.size();
	}	
	
	/**
	 * Encapsulates a chromosome with each fitness value. Allows comparing two
	 * chromosomes according to their fitness
	 */
	private class ChromosomeFitnessPair implements
			Comparable<ChromosomeFitnessPair> {

		Chromosome chromosome;
		Double fitness;

		/**
		 * Constructor- creates a new pair of a chromosome and its fitness value
		 * @param chromosome chromosome object for the pair
		 * @param fitness fitness value of given chromosome
		 */
		public ChromosomeFitnessPair(Chromosome chromosome, double fitness) {
			super();
			this.chromosome = chromosome;
			this.fitness = fitness;
		}

		/**
		 * Compares two wrapped chromosomes according to their fitness value.
		 * Result can be:
		 * -1, if this object is less than given object
		 * 0, if both objects are equal
		 * 1, if this object is greater than given object
		 */
		@Override
		public int compareTo(ChromosomeFitnessPair o) {
//			return (-1) * fitness.compareTo(o.fitness); // TODO
			return fitness.compareTo(o.fitness); // TODO
		}
	}
}